package com.jsftoolkit.base.renderer;

import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.Map.Entry;

import javax.faces.component.EditableValueHolder;
import javax.faces.component.NamingContainer;
import javax.faces.component.UIComponent;
import javax.faces.component.UIData;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.context.ResponseWriterWrapper;
import javax.faces.render.Renderer;

import org.xml.sax.SAXException;

import com.jsftoolkit.ajax.AjaxContext;
import com.jsftoolkit.base.RequiresResources;
import com.jsftoolkit.base.ResourceInfo;
import com.jsftoolkit.gen.info.DecodeInfo;
import com.jsftoolkit.utils.NullWriter;
import com.jsftoolkit.utils.Utils;
import com.jsftoolkit.utils.xmlpull.AttributeEvent;
import com.jsftoolkit.utils.xmlpull.BodyText;
import com.jsftoolkit.utils.xmlpull.EndElement;
import com.jsftoolkit.utils.xmlpull.PullEvent;
import com.jsftoolkit.utils.xmlpull.PullEventSource;
import com.jsftoolkit.utils.xmlpull.StartElement;

/**
 * Base class for renderers. An {@link HtmlRenderer} instance parses an XHTML
 * fragment when it is created, and when asked to render, it replays the SAX
 * events received to the {@link javax.faces.context.ResponseWriter},
 * substituting component properties for occurrences of ${propertyName} in
 * attributes and body text.
 * <p>
 * e.g. You might give the constructor a template like:
 * <p>
 * <code>&lt;div id=&quot;${id}&quot;&gt;Hello ${name|World}!&lt;/div&gt;<br/> 
 * &lt;hr/&gt;</code>
 * <p>
 * A few things about the template:
 * <ul>
 * <li>It may contain more than one root element, other than that exception, it
 * must be well formed XML. Note that most AJAX frameworks will expect your
 * component to render a single top level element with its clientId as the
 * element id, if you don't put everything inside this top level element, it may
 * not render properly in AJAX requests.
 * <li>It uses ${expressions} to insert component properties.
 * <li>A default value for an expression can be specified using the form
 * ${propertyName|Default Value}.
 * </ul>
 * 
 * The above template would render something like:
 * <p>
 * <code>&lt;div id=&quot;foo&quot;&gt;Hello Bob!&lt;/div&gt;<br/> &lt;hr/&gt;</code>
 * <p>
 * Assuming that the component's name and id properties evaluated to Bob and foo
 * respectively.
 * <p>
 * You can also render child components:
 * <p>
 * <code>&lt;div&gt;Before Children &lt;children/&gt; After Children&lt;/div&gt;</code>
 * </p>
 * When combined with a component that has the child &lt;h:outputText
 * value="Children"/&gt; produces:
 * <p>
 * <code>
 * &lt;div&gt;Before Children Children! After Children&lt;/div&gt;
 * </code>
 * </p>
 * Note that children elements following the first will be ignored. This is not
 * a renderer for {@link UIData} or a {@link NamingContainer}, so rendering
 * child components more than once would cause there to be duplicate ids in the
 * output.
 * <p>
 * Lastly, it may be desirable to allow the user to specify what type of tag is
 * output by your component. e.g. render a li instead of a div. You can
 * accomplish this by including a tag, where the name is prefixed with 'tag-'
 * and the rest of the name is the default tag, e.g.
 * <p>
 * <code>
 * &lt;tag-div&gt;Foo&lt;/tag-div&gt;
 * </code>
 * <p>
 * If the component has the 'tag' attribute set to 'span', it would render:
 * <p>
 * <code>
 * &lt;span&gt;Foo&lt;/span&gt;
 * </code>
 * <p>
 * Otherwise:
 * <p>
 * <code>
 * &lt;div&gt;Foo&lt;/div&gt;
 * </code>
 * <h2>Advanced Templates - Callbacks</h2>
 * Sometimes a template wont be enough. It may be necessary to have finer
 * grained control over certain parts of rendering. The good news is that you
 * can still use {@link HtmlRenderer}.
 * <p>
 * {@link HtmlRenderer} provides a {@link RenderCallback} mechanism to allow
 * your code to take over at arbitrary points. Any tag name prefixed with
 * {@link #CALLBACK_PREFIX} will be considered a callback. The name of the
 * callback is the part of the string following the prefix. e.g. for 'call-foo',
 * the callback name is 'foo'.
 * <p>
 * You callback can do anything that a {@link Renderer} is allowed to do.
 * However, because your callback may enclose template text, which cannot be
 * directly modified/removed by the callback, a degree of 'creativity' may
 * sometimes be necessary. For example, to prevent template text enclosed in the
 * callback from being rendered,
 * {@link RenderCallback#encodeBegin(FacesContext, UIComponent)} could call
 * {@link #setNoOpResponseWriter(FacesContext)} and
 * {@link RenderCallback#encodeEnd(FacesContext, UIComponent)} could call
 * {@link #removeNoOpResponseWriter(FacesContext)}.
 * <p>
 * Note that if your callback encloses the {@link #CHILDREN} tag, it is
 * responsible for rendering the child components when
 * {@link RenderCallback#encodeChildren(FacesContext, UIComponent)} is called.
 * 
 * <h2>Including External Resources</h2>
 * You component probably has some sort of javascript support that needs to be
 * included, and maybe even a default stylesheet. This is accomplished by adding
 * an instance of {@link ResourceInfo} to {@link #resources}.
 * <h3>Decoding Inputs</h3>
 * If your component implements {@link EditableValueHolder}, you can have
 * {@link EditableValueHolder#setSubmittedValue(Object)} called with a request
 * parameter specified by the decode attribute (on any element). e.g. <code>
 *  &lt;div id="${id}" decode="${id}:foo-${someOtherProp}" ...
 *  </code>
 * 
 * @author noah
 * 
 */
public abstract class HtmlRenderer extends Renderer implements
		RequiresResources {

	public static final String STYLE_CLASS = "styleClass";

	protected DecodeInfo decodeInfo;

	/**
	 * Convenience set of the render attribs. Unmodifiable.
	 */
	public static final Set<String> PASS_THROUGH = Collections
			.unmodifiableSet(Utils.asSet("dir", "lang", "style", STYLE_CLASS,
					"title", "accesskey", "charset", "coords", "hreflang",
					"onblur", "onclick", "ondblclick", "onfocus", "onkeydown",
					"onkeyup", "onkeypress", "onmousedown", "onmousemove",
					"onmouseout", "onmouseover", "onmouseup", "rel", "rev",
					"shape", "tabindex", "target", "type", "nofollow", "for"));

	/**
	 * Well qualified attribute name that the events iterator is saved under on
	 * the component.
	 */
	private static final String STATE = "com.jsftoolkit.base.renderer.HtmlRenderer.STATE";

	/**
	 * Name of the tag that will be replaced with the component's children.
	 */
	public static final String CHILDREN = "children";

	/**
	 * Component attribute that provides the name of the tag to be rendered for
	 * this component.
	 */
	public static final String TAG = "tag";

	/**
	 * Prefix for tag names that may be set by the user.
	 */
	public static final String TAG_PREFIX = "tag-";

	/**
	 * Prefix for tags that indicate a callback should be invoked.
	 */
	public static final String CALLBACK_PREFIX = "call-";

	// record of the events created by parsing the template
	private PullEventSource template;

	// set of resources for RequiresResources
	protected Set<ResourceInfo> resources;

	// map of render callbacks
	private final Map<String, RenderCallback> callbacks = new HashMap<String, RenderCallback>();

	/**
	 * Creates a renderer that requires no resources and no head template.
	 * 
	 * @param template
	 *            the template text
	 * @throws IOException
	 * @throws SAXException
	 */
	@SuppressWarnings("unchecked")
	public HtmlRenderer(String template) throws IOException, SAXException {
		this(template, Collections.EMPTY_SET);
	}

	/**
	 * Creates a renderer that requires the given resources.
	 * 
	 * @param template
	 *            the template text
	 * @param headTemplate
	 *            the text to inject into head (per instance)
	 * @param resources
	 *            the resources that need to be included in the head element
	 *            (for all instances)
	 * @throws IOException
	 * @throws SAXException
	 */
	public HtmlRenderer(String template, Set<ResourceInfo> resources)
			throws IOException, SAXException {
		super();
		this.template = new RenderEventsCollector(template);
		this.resources = resources;
	}

	/**
	 * Registers the given renderer as a {@link RenderCallback}
	 * 
	 * @param name
	 * @param renderer
	 * @return
	 */
	public RenderCallback registerCallback(String name, Renderer renderer) {
		return registerCallback(name, new RendererCallbackAdaptor(renderer));
	}

	/**
	 * Registers the given callback. See the class comment for more information.
	 * 
	 * @param name
	 * @param callback
	 * @return
	 */
	public RenderCallback registerCallback(String name, RenderCallback callback) {
		return callbacks.put(name, callback);
	}

	/**
	 * Eliminates the ambiguity between
	 * {@link #registerCallback(String, RenderCallback)} and
	 * {@link #registerCallback(String, Renderer)}.
	 * 
	 * @param name
	 * @param callback
	 * @return
	 */
	public RenderCallback registerCallback(String name,
			AbstractRenderCallback callback) {
		return registerCallback(name, (RenderCallback) callback);
	}

	/**
	 * If {@link #setDecodeInfo(String, String[], String[])} or
	 * {@link #setDecodeInfo(String)} was called, sets the request parameter
	 * specified as the component's submitted value.
	 */
	@Override
	public void decode(FacesContext context, UIComponent component) {
		super.decode(context, component);
		if (decodeInfo != null) {
			String[] props = decodeInfo.getProps();
			String[] defaults = decodeInfo.getDefaults();
			Object[] values = new Object[props.length];
			for (int i = 0; i < props.length; i++) {
				values[i] = getProperty(context, component, props[i], Utils
						.get(defaults, i));
			}

			String param = String.format(decodeInfo.getFormat(), values);
			((EditableValueHolder) component).setSubmittedValue(context
					.getExternalContext().getRequestParameterMap().get(param));
		}

		// let the AjaxContext decode
		AjaxContext ajax = AjaxContext.getCurrentInstance();
		if (ajax.isAjaxRequest(context)) {
			ajax.decode(context, component);
		}
	}

	/**
	 * 
	 * @param param
	 *            the name of the request parameter to decode
	 */
	public void setDecodeInfo(String param) {
		setDecodeInfo(param, new String[] {}, new String[] {});
	}

	/**
	 * 
	 * @param paramFormat
	 *            the format string to evaluate to get the request parameter
	 * @param props
	 *            the component properties to evaluate and pass to format
	 * @param defaults
	 *            the default values if the properties are null
	 */
	public void setDecodeInfo(String paramFormat, String[] props,
			String[] defaults) {
		this.decodeInfo = new DecodeInfo(props, defaults, paramFormat);
	}

	/**
	 * 
	 * @param context
	 * @param component
	 * @return a set of {@link ResourceInfo} instances describing the resources
	 *         this component needs. They will be included in iteration order,
	 *         so if order is significant, use a {@link LinkedHashSet}.
	 */
	public Set<ResourceInfo> getResources(FacesContext context,
			UIComponent component) {
		return resources;
	}

	/**
	 * @see Renderer#encodeBegin(FacesContext, UIComponent)
	 * 
	 * Renders the template up to the children element. If there is no children
	 * element, then the entire template is rendered in this phase.
	 */
	@Override
	public void encodeBegin(FacesContext context, UIComponent component)
			throws IOException {
		super.encodeBegin(context, component);

		RenderingState state = new RenderingState(template.iterator());
		// save the state on the component, to be retrieved when we encodeEnd.
		component.getAttributes().put(STATE, state);

		// process the render events
		process(state, context, component);
	}

	/**
	 * Encodes the child components. Behavior varies depending on the template.
	 * If the template contains a {@link #CHILDREN} tag, then this method will
	 * be called when the children tag is encountered. If it does not contain a
	 * children tag, this method will be called after the template text has been
	 * rendered.
	 * <p>
	 * If a callback tag encloses the {@link #CHILDREN} tag, then the callback's
	 * {@link RenderCallback#encodeChildren(FacesContext, UIComponent)} method
	 * will be called when this method is invoked.
	 * 
	 * @see RenderCallback#encodeChildren(FacesContext, UIComponent)
	 */
	@Override
	@SuppressWarnings("unchecked")
	public void encodeChildren(FacesContext context, UIComponent component)
			throws IOException {
		RenderingState state = (RenderingState) component.getAttributes().get(
				STATE);
		RenderCallback callback;
		// if there is a callback registered that encloses the children, have it
		// encode the children, otherwise have the children render themselves.
		if (state.callbackStack.size() > 0
				&& (callback = callbacks.get(state.callbackStack.peek())) != null) {
			callback.encodeChildren(context, component);
		} else {
			super.encodeChildren(context, component);
		}
	}

	/**
	 * Encodes anything following the {@link #CHILDREN} tag, if it exists.
	 * Otherwise, this method should have no effect.
	 */
	@Override
	@SuppressWarnings("unchecked")
	public void encodeEnd(FacesContext context, UIComponent component)
			throws IOException {
		super.encodeEnd(context, component);

		RenderingState state = (RenderingState) component.getAttributes()
				.remove(STATE);
		// keep going until we process all the events, even if processing stops
		// in the middle for some reason
		while (state.events.hasNext()) {
			process(state, context, component);
		}
	}

	/**
	 * Returns true.
	 * 
	 * @see {@link #encodeChildren(FacesContext, UIComponent)}
	 */
	@Override
	public boolean getRendersChildren() {
		return true;
	}

	/**
	 * Does the rendering work. Events are processed until there are no more
	 * events, or a {@link #CHILDREN} element is encountered.
	 * 
	 * @param state
	 * @param context
	 * @param component
	 * @throws IOException
	 */
	protected void process(RenderingState state, FacesContext context,
			final UIComponent component) throws IOException {
		if (state == null) {
			return;
		}

		Map<String, Object> attribs = component.getAttributes();

		Iterator<PullEvent> events = state.events;
		// label this loop so we can easily break out of it when we encounter a
		// children element
		process: while (events.hasNext()) {
			PullEvent event = events.next();
			// respect decoration, reload the response writer for every event
			ResponseWriter writer = context.getResponseWriter();

			// for efficiency, switch on the event type
			// XXX this may be a preemptive optimization as the compiler should
			// be able to optimize an if-else-instanceof into the same thing
			// but then again, it may not.
			switch (event.getType()) {
			case StartElement.TYPE: {
				checkScripts(state, context, component);

				StartElement sEv = (StartElement) event;

				String name = sEv.getName();
				name = getTag(attribs, name);

				if (CHILDREN.equalsIgnoreCase(name)) {
					// break if we hit the children tag
					break process;
				}

				RenderCallback callback = getCallback(state, context,
						component, name);
				if (callback == null) {
					// otherwise, write the start tag
					writer.startElement(name, component);
					state.openCount++;
				} else {
					// invoke the callback
					callback.encodeBegin(context, component);
				}

			}
				break;
			case AttributeEvent.TYPE: {
				AttributeEvent ae = (AttributeEvent) event;
				writer.writeAttribute(ae.getName(), ae.getValue(), null);
			}
				break;
			case PassThrough.TYPE: {
				PassThrough pt = (PassThrough) event;
				writePassThroughAttribs(writer, attribs, pt.getAllowed());
			}
				break;
			case VarAttribEvent.TYPE: {

				// write an attribute that embeds 1 or more component property
				// values
				VarAttribEvent vae = (VarAttribEvent) event;
				List<String> props = vae.getProperties();
				List<String> defaults = vae.getDefaultValues();

				Object[] values = new Object[props.size()];
				for (int i = 0; i < props.size(); i++) {
					values[i] = getProperty(context, component, props.get(i),
							Utils.toString(defaults.get(i)));
				}

				String text = String.format(vae.getPattern(), values);
				// don't render empty attributes (means they were null)
				if (!Utils.isEmpty(text)) {
					writer.writeAttribute(vae.getName(), text, props.get(0));
				}
			}
				break;
			case BodyText.TYPE: {
				checkScripts(state, context, component);

				// write plain text
				BodyText bte = (BodyText) event;

				if (bte.getText() != null) {
					writer.writeText(bte.getText(), null);
				}
			}
				break;
			case VarTextEvent.TYPE: {
				checkScripts(state, context, component);

				// write text from a component property
				VarTextEvent vte = (VarTextEvent) event;
				Object text = getProperty(context, component,
						vte.getProperty(), vte.getDefaultValue());
				if (text != null) {
					writer.writeText(text, vte.getProperty());
				}
			}
				break;
			case EndElement.TYPE: {
				checkScripts(state, context, component);

				// close an element tag
				EndElement e = (EndElement) event;

				String name = getTag(attribs, e.getName());
				RenderCallback callback = getCallback(state, context,
						component, name);
				if (!CHILDREN.equalsIgnoreCase(name)) {
					if (callback == null) {
						writer.endElement(name);
						state.openCount--;
					} else {
						// the XML template would not have parsed if it was not
						// well formed, so there must be a closing callback tag
						assert state.callbackStack.size() > 0;
						assert callback == callbacks.get(state.callbackStack
								.pop());
						callback.encodeEnd(context, component);
					}
				}
			}
				break;
			case DecodeEvent.TYPE:
				// nothing to do for a decode event here
				break;
			default:
				throw new IllegalArgumentException(
						"Unknown event type for event " + event);
			}
		}
	}

	protected void checkScripts(RenderingState state, FacesContext context,
			UIComponent component) throws IOException {
		// everything must be rendered inside the top level element, so make
		// sure we are inside an open tag
		if (!state.scriptsRendered && state.openCount > 0) {
			// make sure all of our includes have been written out. This is a
			// failsafe behavior, in case HtmlScripts was not rendered/replaced.
			ResourceUtils.writeIncludes(context, component, getResources(
					context, component), ResourceUtils
					.getRenderedResources(context));
			state.scriptsRendered = true;
		}
	}

	/**
	 * 
	 * @param state
	 * @param context
	 * @param component
	 * @param name
	 * @return the registered {@link RenderCallback} (if any).
	 */
	protected RenderCallback getCallback(RenderingState state,
			FacesContext context, final UIComponent component, String name) {
		// see if the tag is a callback
		if (name.startsWith(CALLBACK_PREFIX)) {
			// lookup the callback from the registered callbacks
			String realName = name.substring(CALLBACK_PREFIX.length());
			state.callbackStack.push(realName);
			return callbacks.get(realName);
		}
		return null;
	}

	/**
	 * 
	 * @param context
	 * @param component
	 * @param property
	 * @param defaultValue
	 * @return the value of the given property. Handles DI by calling
	 *         {@link UIComponent#getClientId(FacesContext)}.
	 */
	protected Object getProperty(FacesContext context,
			final UIComponent component, String property, String defaultValue) {
		Object value;
		// id must be handled differently
		if ("id".equals(property)) {
			value = component.getClientId(context);
		} else {
			Object propertyValue = component.getAttributes().get(property);
			if (Utils.isEmpty(propertyValue)) {
				value = defaultValue;
			} else {
				value = propertyValue;
			}
		}
		return value;
	}

	/**
	 * Resolves the name of the tag from the component's attributes. See the
	 * class comment for details.
	 * 
	 * @param attribs
	 * @param name
	 *            the tag name
	 * @return the name the tag should use.
	 */
	public static String getTag(Map<String, Object> attribs, String name) {
		if (name.startsWith(TAG_PREFIX)) {
			return (String) Utils.getValue(attribs.get(TAG), name
					.substring(TAG_PREFIX.length()));
		}
		return name;
	}

	/**
	 * Writes an attribute for each entry in attribs that is also in allowed.
	 * Also converts styleClass to class (if it is allowed).
	 * 
	 * @param writer
	 *            the writer to use
	 * @param attribs
	 *            the component's attributes
	 * @param allowed
	 *            attributes to allow
	 * @throws IOException
	 */
	public static void writePassThroughAttribs(ResponseWriter writer,
			Map<String, Object> attribs, Set<String> allowed)
			throws IOException {
		if (attribs.containsKey(STYLE_CLASS) && allowed.contains(STYLE_CLASS)) {
			writer.writeAttribute("class", attribs.get(STYLE_CLASS),
					STYLE_CLASS);
		}

		// pass through the other attributes
		for (Entry<String, Object> attrib : attribs.entrySet()) {
			String key = attrib.getKey();
			if (allowed.contains(key) && !STYLE_CLASS.equals(key)) {
				writer.writeAttribute(key, attrib.getValue(), key);
			}
		}
	}

	/**
	 * Writes all the attributes defined in {@link #PASS_THROUGH}.
	 * 
	 * @param writer
	 * @param attribs
	 * @throws IOException
	 */
	public static void writePassThroughAttribs(ResponseWriter writer,
			Map<String, Object> attribs) throws IOException {
		writePassThroughAttribs(writer, attribs, PASS_THROUGH);
	}

	protected static class RenderingState {
		public RenderingState(Iterator<PullEvent> events) {
			this.events = events;
		}

		public Iterator<PullEvent> events;

		public Stack<String> callbackStack = new Stack<String>();

		/**
		 * Count of the number of open elements.
		 */
		public int openCount;

		public boolean scriptsRendered;
	}

	/**
	 * {@link ResponseWriter} decorator. Redirects all output to a
	 * {@link NullWriter}.
	 * 
	 * @author noah
	 * 
	 */
	public static class NoOpResponseWriter extends ResponseWriterWrapper {

		// the wrapper writer
		private final ResponseWriter writer;

		// the original response writer
		private final ResponseWriter original;

		public NoOpResponseWriter(ResponseWriter original) {
			super();
			this.original = original;
			this.writer = original.cloneWithWriter(new NullWriter());
		}

		@Override
		protected ResponseWriter getWrapped() {
			return writer;
		}

		public ResponseWriter getOriginal() {
			return original;
		}

	}

	/**
	 * Decorates the current response writer with a {@link NoOpResponseWriter}.
	 * Can be reversed by calling
	 * {@link #removeNoOpResponseWriter(FacesContext)}.
	 * 
	 * @param context
	 */
	public static void setNoOpResponseWriter(FacesContext context) {
		context.setResponseWriter(new NoOpResponseWriter(context
				.getResponseWriter()));
	}

	/**
	 * Removes a single {@link NoOpResponseWriter} decorating the response
	 * writer obtained from context. This method will need to be called multiple
	 * times if multiple {@link NoOpResponseWriter}s have been set.
	 * 
	 * @param context
	 * @return true if a {@link NoOpResponseWriter} was removed, false if the
	 *         response writer remained unchanged.
	 */
	public static boolean removeNoOpResponseWriter(FacesContext context) {
		ResponseWriter responseWriter = context.getResponseWriter();
		if (responseWriter instanceof NoOpResponseWriter) {
			NoOpResponseWriter noop = (NoOpResponseWriter) responseWriter;
			context.setResponseWriter(noop.getOriginal());
			return true;
		}
		return false;

	}

	/**
	 * Concatenates the given templates together.
	 * 
	 * @param encoding
	 * @param templates
	 *            the paths to the classpath resources to load
	 * @return
	 * @throws IOException
	 *             if there is a problem reading one of the templates
	 */
	public static String combineTemplates(String encoding, String... templates)
			throws IOException {
		StringBuilder sb = new StringBuilder();
		for (String resource : templates) {
			sb.append(Utils.resourceToString(resource, encoding));
		}
		return sb.toString();
	}
}
